# one token or term in a search
class SearchToken
  FIELDS = %w(sourcename sourcetype title content author before after pertinent report-id duplicate-of)
  REGEXP_FIELDS = %w(sourcename sourcetype title content author)
  DEFAULT_FIELDS = %w(title content author)
  def initialize(str)
    @str = str
  end
  
  # returns the sql fragment expression for this term
  def sql
    # if this token is a boolean operator, just echo it back
    return @str if %w[and or not].include?(@str)
    
    # if this token begins or ends with a parenth, save it.
    @str.gsub!(/^(\()/, "")
    oparenth = $1 || ""
    @str.gsub!(/(\))$/, "")
    cparenth = $1 || ""
    
    # if this token contains a qualifier
    if @str.match(/^([\w\-]+):(.+)$/)
      qualifier, term = $1, $2
      # raise an error if the qualifier is invalid
      raise "Invalid search qualifier '#{qualifier}'" unless FIELDS.include?(qualifier)
      fields = [qualifier]
    else
      # else, search default fields
      fields = DEFAULT_FIELDS
      term = @str
    end
    
    # if the term is a regular expression, set a flag and remove the /'s
    if term.match(/\/(.+)\//)
      raise "Regular expressions not allowed for #{fields.first}" unless REGEXP_FIELDS.include?(fields.first)
      regexp = true
      term = $1
    else
      regexp = false
    end
    
    # for each field, create a fragment
    fragments = []
    extras = ""
    fields.each do |f|
      case f
      when 'duplicate-of' then col = "MATCH (reports.content)"; op = "AGAINST"; extras = " IN BOOLEAN MODE"
        t = "\"#{Report.find(term).characteristic}\"" or raise "Report ##{id} is too short to check for duplicates"
      when 'pertinent' then col = "reports.important"; op = "="; t = term
      when 'report-id' then col = "reports.id"; op = "="; t = term
      when 'sourcename' then col = "sources.name"; op = "="; t = term
      when 'sourcetype' then col = "sources.type"; op = "="; t = "#{term}Source"
      when 'author' then col = "reports.author"; op = "like"; t = "%#{term}%"
      when 'title', 'content' then col = "reports.#{f}"; op = "like"; t = regexp ? term : "%#{term}%"
      when 'before', 'after' then col = "reports.authored_at"; op = f == "before" ? "<" : ">="; t = Time.zone.parse(term) or raise "#{term} is not a valid time"
      end
      op = "rlike" if regexp
      fragments << sanitize("#{col} #{op} (?#{extras})", t)
    end
    
    # join fragments with 'or' (note this is only for one token.. actual boolean operators are separate)
    oparenth + "(" + fragments.join(' or ') + ")" + cparenth
  end
  
  # sanitizes a string and substitutes given values for ?'s
  # TODO: cleaner way to do this
  def sanitize(*args)
    ActiveRecord::Base.__send__(:sanitize_sql, args, '')
  end
end
